﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MonoStrategy
{
    public enum BuildStates
    {
        Started,
        Graded,
        Built,
        Completed
    }

    public class BuildTask 
    {
        private static readonly CrossRandom m_Random = new CrossRandom(0);
        private static readonly TerrainBrush m_Brush = TerrainBrush.CreateSphereBrush(3, 3);
        private const int StepsPerTimber = 20;
        private const int StepsPerStone = 30;

        internal int UsedTimberCount { get; private set; }
        internal int UsedStoneCount { get; private set; }
        private GenericResourceStack m_StoneQuery;
        private GenericResourceStack m_TimberQuery;
        private List<Movable> m_Graders = new List<Movable>();
        private GradingInfo m_GradingInfo;
        private int m_RemainingGradingSteps;
        private int m_InitialGradingSteps;
        private int m_RemainingConstructorSteps;
        private int m_InitialConstructorSteps;
        private ConstructorSpot[] m_ConstructorSpots;
        private int m_ConstructorCount = 0;
        private int m_LastConstructorSteps;
        private BuildStates m_PrevState = BuildStates.Started;
        private Double m_Progress = 0;
        private readonly UniqueIDObject m_Unique;

        internal long UniqueID { get { return m_Unique.UniqueID; } }
        internal bool AreGradersMarkedForRelease { get; set; }
        internal bool AreConstructorsMarkedForRelease { get; set; }
        public IEnumerable<GenericResourceStack> Queries { get; private set; }
        public Object UserContext { get; set; }
        internal LinkedListNode<BuildTask> Node { get; set; }
        public BaseBuilding Building { get; private set; }
        public TerrainDefinition Terrain { get { return Building.Parent.Terrain; } }
        public BuildingConfig Config { get { return Building.Config; } }
        internal BuildingManager Manager { get; private set; }
        internal ResourceManager ResMgr { get { return Manager.ResMgr; } }
        internal MovableManager MovMgr { get { return Manager.MovMgr; } }
        public Double Progress
        {
            get { return m_Progress; }
            private set
            {
                if (m_Progress == value)
                    return;

                m_Progress = value;

                if (OnProgressChanged != null)
                    OnProgressChanged(this, value);
            }
        }
        public bool IsGraded { get { return m_RemainingGradingSteps <= 0; } }
        public bool IsBuilt { get { return m_RemainingConstructorSteps <= 0; } }
        public bool HasWorker { get; private set; }
        public int MaximumGraders { get; private set; }
        public bool IsCompleted { get; private set; }
        public bool HasGrader { get; private set; }
        public bool IsSuspended { get; internal set; }
        public BuildStates State
        {
            get
            {
                if (!IsGraded) return BuildStates.Started;
                if (!IsBuilt) return BuildStates.Graded;
                if (!IsCompleted) return BuildStates.Built;
                return BuildStates.Completed;
            }
        }

        public event Procedure<BuildTask> OnStateChanged;
        public event Procedure<BuildTask, Double> OnProgressChanged;

        private class ConstructorSpot
        {
            internal Point Position;
            internal Direction Direction;
            internal Movable Constructor;
        }

        public void Dispose()
        {
            if (m_TimberQuery != null)
            {
                Manager.ResMgr.RemoveResource(m_TimberQuery);
                m_TimberQuery = null;
            }

            if (m_StoneQuery != null)
            {
                Manager.ResMgr.RemoveResource(m_StoneQuery);
                m_StoneQuery = null;
            }

            ReleaseConstructors();
            ReleaseGraders();
        }

        internal BuildTask(BuildingManager inManager, BaseBuilding inBuilding)
        {
            if ((inBuilding == null) || (inManager == null))
                throw new ArgumentNullException();

            m_Unique = new UniqueIDObject(this);
            Manager = inManager;
            Building = inBuilding;
            Queries = new GenericResourceStack[0];
            MaximumGraders = 5;
            m_GradingInfo = Terrain.GetGradingInfo(Building.Position.XGrid, Building.Position.YGrid, Config);
            m_InitialGradingSteps = m_RemainingGradingSteps = m_GradingInfo.Variance / m_Brush.Variance;
            m_InitialConstructorSteps = m_RemainingConstructorSteps =
                (Config.WoodCount * StepsPerTimber + Config.StoneCount * StepsPerStone);

            m_LastConstructorSteps = m_InitialConstructorSteps + Math.Min(StepsPerTimber, StepsPerStone) / 2;

            // prepare constructor spots
            List<ConstructorSpot> constSpots = new List<ConstructorSpot>();

            foreach (var pos in Config.ConstructorSpots)
            {
                constSpots.Add(new ConstructorSpot() { Position = new Point(pos.X + Building.Position.XGrid, pos.Y + Building.Position.YGrid) });
            }

            // compute constructor directions
            int maxY = constSpots.Max(e => e.Position.Y);
            ConstructorSpot maxYSpot = constSpots.First(e => e.Position.Y == maxY);
            int iSpot = 0;

            for (; iSpot < constSpots.Count; iSpot++)
            {
                if (maxYSpot == constSpots[iSpot])
                    break;

                constSpots[iSpot].Direction = Direction._045;
            }

            for (; iSpot < constSpots.Count; iSpot++)
            {
                constSpots[iSpot].Direction = Direction._315;
            }

            m_ConstructorSpots = constSpots.ToArray();

            // mines are not graded
            if (Building.Config.ClassType == typeof(MineBuilding))
            {
                m_RemainingGradingSteps = 0;

                AddResourceQueries();
            }
        }

        void AddResourceQueries()
        {
            m_TimberQuery = Manager.ResMgr.AddResourceStack(
                CyclePoint.FromGrid(
                    Building.Position.XGrid + Config.TimberSpot.X,
                    Building.Position.YGrid + Config.TimberSpot.Y),
                ResStackType.Query,
                Resource.Timber,
                Math.Min(8, Config.WoodCount));

            m_StoneQuery = Manager.ResMgr.AddResourceStack(
                CyclePoint.FromGrid(
                    Building.Position.XGrid + Config.StoneSpot.X,
                    Building.Position.YGrid + Config.StoneSpot.Y),
                ResStackType.Query,
                Resource.Stone,
                Math.Min(8, Config.StoneCount));

            m_TimberQuery.OnCountChanged += Query_OnCountChanged;
            m_StoneQuery.OnCountChanged += Query_OnCountChanged;

            Queries = new GenericResourceStack[] { m_StoneQuery, m_TimberQuery };
        }

        void Query_OnCountChanged(GenericResourceStack inSender, int inOldValue, int inNewValue)
        {
            if (inSender.Resource == Resource.Timber)
                inSender.MaxCount = Math.Min(8, Config.WoodCount - UsedTimberCount);
            else
                inSender.MaxCount = Math.Min(8, Config.StoneCount - UsedStoneCount);
        }


        internal void ReleaseConstructors()
        {
            m_ConstructorCount = 0;

            foreach (var spot in m_ConstructorSpots)
            {
                if (spot.Constructor == null)
                    continue;

                spot.Constructor.Job = null;
                spot.Constructor.Stop();
                spot.Constructor = null;
            }

            AreConstructorsMarkedForRelease = false;
        }

        internal void ReleaseGraders()
        {
            foreach (var grader in m_Graders)
            {
                grader.Job = null;
                grader.Stop();
            }

            m_Graders.Clear();

            AreGradersMarkedForRelease = false;
        }

        internal void Update()
        {
            if (!IsGraded)
            {
                // update progress
                Progress = Math.Max(0, Math.Min(1.0, 1.0 - (m_RemainingGradingSteps / (double)m_InitialGradingSteps)));

                if (IsSuspended)
                    return;

                // look for graders if necessary
                while (MaximumGraders - m_Graders.Count > 0)
                {
                    Movable grader = MovMgr.FindFreeMovableAround(Building.Position.ToPoint(), MovableType.Grader);

                    if (grader == null)
                        break;

                    if (!HasGrader)
                    {
                        HasGrader = true;

                        AddResourceQueries();
                    }

                    m_Graders.Add(grader);

                    grader.Job = new JobGrading(grader, this);
                }
            }
            else if (!IsBuilt)
            {
                AreGradersMarkedForRelease = true;

                // update resource queries
                bool hasResources = (m_TimberQuery.Available > 0) || (m_StoneQuery.Available > 0);
                int stepDiff = m_LastConstructorSteps - m_RemainingConstructorSteps;

                if (m_TimberQuery.Available > 0)
                {
                    if (stepDiff > StepsPerTimber)
                    {
                        m_TimberQuery.RemoveResource();
                        m_LastConstructorSteps -= StepsPerTimber;
                        UsedTimberCount++;

                        m_TimberQuery.MaxCount = Math.Min(8, Config.WoodCount - UsedTimberCount);
                    }
                }
                else if (m_StoneQuery.Available > 0)
                {
                    if (stepDiff > StepsPerStone)
                    {
                        m_StoneQuery.RemoveResource();
                        m_LastConstructorSteps -= StepsPerStone;
                        UsedStoneCount++;

                        m_StoneQuery.MaxCount = Math.Min(8, Config.StoneCount - UsedStoneCount);
                    }
                }

                if (hasResources && !IsSuspended)
                {
                    // look for constructors if necessary 
                    while (m_ConstructorSpots.Length - m_ConstructorCount > 0)
                    {
                        Movable constructor = MovMgr.FindFreeMovableAround(Building.Position.ToPoint(), MovableType.Constructor);
                        var freeSpot = m_ConstructorSpots.First(e => e.Constructor == null);

                        if (constructor == null)
                            break;

                        m_ConstructorCount++;

                        freeSpot.Constructor = constructor;

                        if (constructor.Job != null)
                            throw new ApplicationException();

                        constructor.Job = new JobConstructing(constructor, this, freeSpot.Position, freeSpot.Direction);
                    }
                }
                else
                {
                    AreConstructorsMarkedForRelease = true;
                }

                if ((UsedTimberCount >= Config.WoodCount) && (UsedStoneCount >= Config.StoneCount))
                    m_RemainingConstructorSteps = 0;

                // update progress
                Progress = Math.Max(0, Math.Min(1.0, 1.0 - (m_RemainingConstructorSteps / (double)m_InitialConstructorSteps)));
            }
            else if (!HasWorker)
            {
                AreConstructorsMarkedForRelease = true;

                Progress = 1.0;

                if (IsSuspended)
                    return;

                if (Config.Worker == BuildingWorkerType.None)
                {
                    IsCompleted = true;
                    HasWorker = true;

                    Building.MarkAsReady();

                    return;
                }

                Movable settler = null;
                OneTimeJob job = null;

                if (Config.Worker != BuildingWorkerType.Settler)
                {
                    // find tool around building
                    GenericResourceStack resource = ResMgr.FindResourceAround(Building.Position.ToPoint(), Config.WorkerTool, ResStackType.Provider);

                    if (resource != null)
                    {
                        // find free settler around tool and recruit him
                        settler = MovMgr.FindFreeMovableAround(resource.Position.ToPoint(), MovableType.Settler);

                        if (settler != null)
                        {
                            settler.Job = job = new JobWorkerRecruting(settler, resource, Building);

                            HasWorker = true;
                        }
                    }
                }
                else
                {
                    settler = MovMgr.FindFreeMovableAround(Building.Position.ToPoint(), MovableType.Settler);

                    if (settler != null)
                    {
                        settler.Job = job = new JobWorkerAssignment(settler, Building);

                        HasWorker = true;
                    }
                }

                if (job != null)
                {
                    job.OnCompleted += (unused, succeeded) =>
                    {
                        if (!succeeded)
                        {
                            settler.Job = null;

                            return;
                        }

                        Building.MarkAsReady();

                        MovMgr.MarkMovableForRemoval(settler);
                        IsCompleted = true;
                    };
                }
            }

            if ((m_PrevState != State) && (OnStateChanged != null))
                OnStateChanged(this);

            m_PrevState = State;
        }

        internal bool DoConstruct()
        {
            m_RemainingConstructorSteps--;

            return !IsBuilt;
        }

        internal void DoGrade(Point inCell)
        {
            m_RemainingGradingSteps--;

            Terrain.SetFlagsAt(inCell.X, inCell.Y, TerrainCellFlags.Grading);
            Terrain.AverageDrawToHeightmap(inCell.X, inCell.Y, m_GradingInfo.AvgHeight, m_Brush);
        }
    }
}
